/*
 *  Copyright 2011 Iurii Dunko, Iana Potochniak.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package org.opu.yyminijava.symboltable;

/**
 *
 * @author Iurii Dunko
 * @author Iana  Potochniak
 */
public class SymbolTable {

    /** Root of scope tree */
    private Scope root;
    /** Current scope */
    private Scope current;

    /** The packages where to this class looked when he couldn't find smth */
    public static final String[] nativePackages = new String[]{
        "java.lang.", "java.util.", "java.io."
    };

    public SymbolTable() {
        root = new Scope(null);
        current = root;
    }

    public void enterScope() {
        current = current.nextChild();
    }

    public void exitScope() {
        current = current.getParent();
    }

    public void put(Record<?> item) {
        current.put(item);
    }

	@SuppressWarnings("unchecked")
	public <T, R extends Record<T>> R lookup(R key) {
        return (R) current.lookup(key);
    }

    public void printTable() {
        root.print();
    }

    public void resetTable() {
        root.resetScope();
    }

    public ClassRecord getThis() {
        Scope scope = getCurrenClassScope();
        if (scope != null) {
            return getClass(scope.getName());
        } else {
            return null;
        }
    }

    public ClassRecord getClass(String name) {
        Record<?> lookup = root.lookup(new ClassRecord(name));
        if (lookup == null){
            root.put(lookup = lookupInNative(name));
        }
        return (ClassRecord) lookup;
    }

    /**
     * For scope with the name 'scope' define default scope with the name of
     * 'defScope'.
     * @param scope name of scope for which will be added default scope
     * @param defName name of scope that will be added as a default scope
     * @return true if the value was defined.
     */
    public boolean addDefaultScope(String scope, String defName) {
        Scope to = root.getChildrenWithName(scope);
        Scope what = root.getChildrenWithName(defName);

        if (to != null && what != null) {
            to.setDefaultScope(what);
            return true;
        } else {
            return false;
        }
    }

    private Scope getCurrenClassScope() {
        Scope tmpCrnt = current;
        if (tmpCrnt.getParent() == null) {
            return null;
        }

        while (tmpCrnt.getParent().getParent() != null) {
            tmpCrnt = tmpCrnt.getParent();
        }

        return tmpCrnt;
    }

    private ClassRecord lookupInNative(String name) {
        // TODO: Create exception structure, don't use java exception
        // Using error is temporary solution, we use exception when the
        // incoming file contains errors, when the problem in our compiler
        // we use errors (but it for now)
        if (name.indexOf('.') != -1) {
            try {
                return getNativeClass(Class.forName(name));
            } catch (ClassNotFoundException ex) {
                throw new IllegalArgumentException("Wrong data type " + name, ex);
            }
        } else {
            for (String p : nativePackages) {
                try {
                    return getNativeClass(Class.forName(p + name));
                } catch (ClassNotFoundException ex) {
                }
            }
            throw new IllegalArgumentException("Class " + name + " not found.");
        }
    }

    private ClassRecord getNativeClass(Class<?> clazz) {
        return new NativeClassRecord(clazz);
    }
}
